/**
 * 在三个.js场景中使用的材料。
 */
import * as THREE from 'three';

const textureLoader = new THREE.TextureLoader();

const loadRepeatedTexture = (url) =>
  textureLoader.load(`static/${url}`, texture => {
    texture.wrapS = THREE.RepeatWrapping;
    texture.wrapT = THREE.RepeatWrapping;
  });

const railroadTie = loadRepeatedTexture('/rail64.png');
const sidewalkTexture = loadRepeatedTexture('/sidewalk256.jpg');
const asphaltTexture = loadRepeatedTexture('/asphalt256.jpg');
const crossingTexture = loadRepeatedTexture('/zebra.jpg');

export const LAND = new THREE.MeshPhysicalMaterial({
  color: 0x888888,
  metalness: 0.1,
  roughness: 1.0,
  clearCoat: 0.1,
  clearCoatRoughness: 1.0,
  reflectivity: 0.05,
  // We use polygonOffset to counter z-fighting with roadways.
  polygonOffset: true,
  polygonOffsetFactor: +2,
  polygonOffsetUnits: 1,
});
export const WATER = new THREE.MeshPhysicalMaterial({
  side: THREE.DoubleSide,
  color: 0xaaaaaa,
  metalness: 0,
  roughness: 0.8,
  clearCoat: 0.7,
  clearCoatRoughness: 0.5,
  reflectivity: 0.9,
});
export const BUILDING_TOP = new THREE.MeshPhysicalMaterial({
  color: 0xffffff,
  metalness: 0,
  roughness: 0.8,
  clearCoat: 0.6,
  clearCoatRoughness: 1.0,
  reflectivity: 0.2,
});
export const BUILDING_SIDE = new THREE.MeshPhysicalMaterial({
  color: 0xffffff,
  metalness: 0,
  roughness: 0.8,
  clearCoat: 0.6,
  clearCoatRoughness: 1.0,
  reflectivity: 0.2,
});

export const BUILDING = new THREE.MeshFaceMaterial([BUILDING_TOP, BUILDING_SIDE]);

export const ROAD = new THREE.MeshPhysicalMaterial({
  map: asphaltTexture,
  side: THREE.DoubleSide,
  metalness: 0,
  roughness: 0.8,
  clearCoat: 0.6,
  clearCoatRoughness: 1.0,
  reflectivity: 0.2,
});
export const BUS_STOP = new THREE.MeshPhysicalMaterial({
  side: THREE.DoubleSide,
  color: 0xdddd00,
});
export const CYCLEWAY = new THREE.MeshPhysicalMaterial({
  color: 0xaa0000,
  side: THREE.DoubleSide,
});
export const CROSSING = new THREE.MeshPhysicalMaterial({
  map: crossingTexture,
  side: THREE.DoubleSide, // one side is visible from above, the other casts shadows.
  metalness: 0,
  roughness: 0.8,
  clearCoat: 0.6,
  clearCoatRoughness: 1.0,
  reflectivity: 0.2,
  polygonOffset: true, // this resolves z-fighting between crosswalks and junctions.
  polygonOffsetFactor: -3,
  polygonOffsetUnits: 1,
});
export const RAILWAY = new THREE.MeshPhysicalMaterial({
  map: railroadTie,
  transparent: true,
  side: THREE.DoubleSide,
});
export const WALKWAY = new THREE.MeshPhysicalMaterial({
  map: sidewalkTexture,
  side: THREE.DoubleSide,
});
export const HIGHLIGHT = new THREE.MeshPhysicalMaterial({
  color: 0xff0000,
  depthTest: true,
  polygonOffset: true,
  polygonOffsetFactor: -2,
  polygonOffsetUnits: 1.1,
  transparent: true,
  opacity: 0.9,
  side: THREE.DoubleSide,
});
export const JUNCTION = new THREE.MeshPhysicalMaterial({
  color: 0x373737,
  reflectivity: 0.2,
  polygonOffset: true, // this resolves z-fighting between the junctions and street.
  polygonOffsetFactor: -2,
  polygonOffsetUnits: 1,
});

export const TRAFFIC_LIGHTS = {
  g: new THREE.MeshLambertMaterial({
    color: 0x00ff00,
    side: THREE.DoubleSide,
  }),
  y: new THREE.MeshLambertMaterial({
    color: 0xffff00,
    side: THREE.DoubleSide,
  }),
  r: new THREE.MeshLambertMaterial({
    color: 0xff0000,
    side: THREE.DoubleSide,
  }),
  x: new THREE.MeshLambertMaterial({
    color: 0x000000,
    side: THREE.DoubleSide,
  }),
};
/** 设置uv材质 */
export function addUVMappingToGeometryWithPolyline(
  geometry,points,transform,width,uScaleFactor = 1
) {
  const  position  = geometry.attributes.position.array;
  let uv = []
  for (let i = 0; i < position.length; i+=3) {
    const x = position[i];
    const z = position[i+2];
    const d = polylineDistance(points, [x, transform.top - z]);
    uv.push(d.dLine * uScaleFactor, d.dPerp / width + 0.5)
  }
  return uv
}


export function polylineDistance(
  vertices,
  pt,
  isClosed = false,
){
  let totalD = 0; // total distance along the polyline.
  let closestD= null;

  const lastIndex = isClosed ? vertices.length : vertices.length - 1;
  for (let i = 0; i < lastIndex; i++) {
    
    const a = vertices[i];  
    const b = vertices[(i + 1) % vertices.length];  // 下班不会超过 vertices.length - 1
    const thisD = closestPointOnLineSegment(a, b, pt);
    if (!closestD || thisD.d2 < closestD.d2) {
      closestD = thisD;
      closestD.dLine += totalD;
    }
    const len = distance(a, b);
    totalD += len;
  }

  if (!closestD) {
    throw new Error('polylines must have 2+ vertices');
  }

  return closestD;
}



function sub(a, b) {
  return [a[0] - b[0], a[1] - b[1]];
}

function dot(a, b) {
  return a[0] * b[0] + a[1] * b[1];
}

export function vectorNorm(v) {
  return Math.sqrt(dot(v, v));
}

function scale(v, k) {
  return [v[0] * k, v[1] * k];
}

function distance2(a, b) {
  const dx = a[0] - b[0];
  const dy = a[1] - b[1];
  return dx * dx + dy * dy;
}

function distance(a, b) {
  return Math.sqrt(distance2(a, b));
}


export function closestPointOnLineSegment(
  a,
  b,
  pt,
){
  /** 求差 */
  const bMinusA = sub(b, a);
  /** 求a,b两点间的距离 */
  const bAnorm = vectorNorm(bMinusA);
  const ab = scale(bMinusA, 1 / bAnorm);
  const [dx, dy] = ab;
  const ptA = sub(pt, a);

  const dLine = dx * ptA[0] + dy * ptA[1];
  const dPerp = dx * ptA[1] - dy * ptA[0];

  
  if (dLine < 0) {
    // Closest point is A.
    return {d2: distance2(a, pt), dLine, dPerp};
  } else if (dLine > bAnorm) {
    // Closest point is B.
    return {d2: distance2(pt, b), dLine, dPerp};
  } else {
    // Closest point is along the line.
    return {d2: dPerp * dPerp, dLine, dPerp};
  }
}